Camera の定義とVec4の定義(Rust製ラスタライザ)
https://scrapbox.io/files/66ebbbeeb36563001c782b7c.png
目次
Cameraの実装(と定義した演算のテスト)
右向き上向きを正とする。$ [-1, 1\rbrackを画面中の座標として取る。
これはCameraがすればいいかなappbird.icon Canvasがやる必要はない
正規化するにあたり、座標の変換機能が欲しい
Vec4にimplする形で実装する
できるだけ重複した実装はなくしたい。
行列を実装するか...?
code:rs
impl ops::Add for Vec4 {
type Output = Vec4;
fn add(self, rhs: Self) -> Self::Output {
Vec4::new(
)
}
}
これあと4回書くの?抽象化できない?
Vec4の方にFn(f64, f64) -> f64型の関数渡せるようにして、全ての要素に一つ一つ適用するメソッドを用意しておけば
演算子を定義する側では、クロージャ|a, b|{a + b}を用意しておけばこんなに四つの要素に対して書く必要もない
でもそうするとコストがかさむのでは....
動的に型が決定されるとするなら、演算のたびにその処理が走るって考えると結構辛い
そういえばRustの特徴にゼロコスト抽象化ってあったな、あれってどういう意味なんだろう 動的ディスパッチ:実行時にわかる情報から呼び指すメソッドの実体を選ぶ。柔軟だが実行時コストがかかる
静的ディスパッチ:コンパイル時に決まる型で呼び指すメソッドの実体を選ぶ。使える場面がやや限定されるが実行時コストはかからない
今回は静的ディスパッチだから、ゼロコストなのかな
あとは、コンパイラが関数を展開してくれれば四つベタ書きするのと性能は変わらない
じゃあそのまま抽象化しにかかるか
というわけで、まずメソッドとして二項演算をそれぞれの要素に適用するbinと単項演算を適用するuniを定義する
code:rs
fn bin<F>(lhs:Self, rhs:Self, op:F) -> Self
where
F:Fn(f64, f64) -> f64
{
Vec4::new(
)
}
fn uni<F>(lhs:Self, op:F) -> Self
where
F: Fn(f64) -> f64
{
Vec4::new(
)
}
こうすると、演算子の定義のコードはこう書けて、かなり短く済む。
code:rs
impl ops::Add for Vec4 {
type Output = Vec4;
fn add(self, rhs: Self) -> Self::Output {
Vec4::bin(self, rhs, |a, b|{a + b})
}
}
impl ops::Sub for Vec4 {
type Output = Vec4;
fn sub(self, rhs: Self) -> Self::Output {
Vec4::bin(self, rhs, |a, b|{a - b})
}
}
impl ops::Mul<f64> for Vec4 {
type Output = Vec4;
fn mul(self, rhs: f64) -> Self::Output {
Vec4::uni(self, |a|{a * rhs})
}
}
impl ops::Div<f64> for Vec4 {
type Output = Vec4;
fn div(self, rhs: f64) -> Self::Output {
Vec4::uni(self, |a|{a / rhs})
}
}
// 1.2*Vec4みたいな書き方をしたいので、可換として取り扱っておく
impl ops::Mul<Vec4> for f64 {
type Output = Vec4;
fn mul(self, rhs: Vec4) -> Self::Output { rhs * self }
}
げっ、こうすると所有権のムーブに引っかかるな
code:camera.rs
pub fn transform_into_screen(self, p1: &Vec4) -> Vec4Screen {
Vec4Screen{ 0: (p1 + Vec4::new(1., 1., 0., 0.)) / 2. }
}
&を外すのはできるだけ避けたいが、cloneして良いものか...。
opsのオペランドを&で取ってないのがダメなのか
じゃあそう直すか
code:vec4.rs
impl ops::Add for &Vec4 {
type Output = Vec4;
fn add(self, rhs: Self) -> Self::Output {
Vec4::bin(self, rhs, |a, b|{a + b})
}
}
code:vec4.rs
fn bin<F>(lhs:&Self, rhs:&Self, op:F) -> Self ... { ... }
まあ今はシンプルに済ませよう 演算だけは定義して、ベクトルは定義しないでおく
命名参考
追記:後に、この関数はVec4::construct関数が実装されたことによって削除されるappbird.icon
code:rs
/** i番目の要素がelement(i)であるようなベクトルを構成する。
* ただし、iについて0はx, 1はy, 2はz, 3はw座標をそれぞれ示す。
*/
pub fn construct<F>(element:F) -> Self
where
F:Fn(usize) -> f64
{
Vec4::new(
element(0),
element(1),
element(2),
element(3),
)
}
AC.icon Cameraの実装(と定義した演算のテスト)
code:rs
pub struct Camera {}
impl Camera {
pub fn new() -> Camera { Camera{} }
pub fn draw_line(&mut self, canvas:&mut Canvas, p1:&Vec4Project, p2:&Vec4Project, color:&Color) {
let p1 = self.transform_into_screen(canvas.size(), &p1).to_point2();
let p2 = self.transform_into_screen(canvas.size(), &p2).to_point2();
canvas.draw_line(&p1, &p2, color);
}
pub fn transform_into_screen(&self, size:Point2, p: &Vec4Project) -> Vec4Screen {
let vector_0to1 = (&p.0 + &Vec4::new(1., 1., 0., 0.)) / 2.0;
let screen_size = size.to_vec4();
Vec4Screen(vector_0to1.hadamard(&screen_size))
}
}
なんか歪んでねえ?
そらそう X方向とY方向で拡大率が違う
できれば同じ拡大率にしたい
X方向基準で揃えるようにしてみる
(0, 0)基準での拡大にしたい
(0, 0)が画面の中心に来る
点をh/2倍に拡大してから、点を(w/2, h/2)に並行移動させる。
i.e. 座標系をh/2倍に縮小してから、原点を(-w/2, -h/2)に並行移動させる。
https://scrapbox.io/files/66ebaf759b47e8001db0e527.png
code:rs
pub fn transform_into_screen(&self, size:Point2, p: &Vec4Project) -> Vec4Screen {
let scale = size.y as f64 / 2.;
Vec4Screen(scale * &p.0 + size.to_vec4() / 2.)
}
こう
https://scrapbox.io/files/66ebbbeeb36563001c782b7c.png